﻿using Rites;
using System;
using System.Net;
using System.Net.Sockets;
using System.IO;

public delegate void DataReceivedDlg(byte[] data);
public delegate void UserEventDlg(object sender);

public class Connection
{
    public event UserEventDlg UserDisconnected;
    public event DataReceivedDlg DataReceived;

    //Networking internals
    private TcpClient client;
    private byte[] readBuffer = new byte[Constants.READBUFFERSIZE]; //Raw data buffer
    public long LastSendMessageID;
    public string DataBuffer { get; set; }
    public bool Connected { get; set; }

    public Connection()
    {
    }

    private void StartListening()
    {
        this.client.GetStream().BeginRead(this.readBuffer, 0, Constants.READBUFFERSIZE, this.StreamReceived, null);
    }

    private void StreamReceived(IAsyncResult ar)
    {
        //Note: This method is not executed on the same thread as the one that called BeginRead
        //This means that you have to be careful when you modify data in the DataReceived-event
        int bytesRead = 0;
        try
        {
            //Lock the stream to prevent objects from other threads to acess it at the same time
            //Then call EndRead(ar) where ar is the IAsyncResult the TcpClient sent to us.
            //This will return the number of bytes that has been received
            lock (client.GetStream())
                bytesRead = this.client.GetStream().EndRead(ar);
        }
        catch (Exception)
        { } //If we get an exception bytesread will remain 0 and we will disconnect.

        //If bytesRead is 0 we have lost connection to the server, save yourselves, it's too late for me
        if (bytesRead == 0)
        {
            this.Disconnect();
            return;
        }

        //Create a new buffer with just enough space to fit the received data
        byte[] data = new byte[bytesRead];

        //Copy the data from the readBuffer to data.
        //The reason why we do this instead of sending the entire readBuffer with the DataReceived-event is
        //that we want to start listening for new messages asap. But if we start listening before we call the
        //DataReceived-event and new data is received before the DataReceived-method has finished the 
        //readBuffer will change and the DataReceived-method will process completly corrupt data.
        for (int i = 0; i < bytesRead; i++)
            data[i] = readBuffer[i];

        //Start listening for new data
        this.StartListening();

        //Tell everyone that we've received data.
        if (this.DataReceived != null)
            this.DataReceived(data);
    }

    public Connection(TcpClient client, byte id)
    {
        this.client = client;
        this.client.NoDelay = true;

        this.StartListening();
    }

    public bool Connect(string ip, int port)
    {
        try
        {
            this.client = new TcpClient();
            this.client.NoDelay = true;
            this.client.Connect(ip, port);
            this.StartListening();
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }

    public void Disconnect()
    {
        if (this.Connected)
        {
            this.Connected = true;
            try
            {
                if (this.client != null)
                {
                    if (this.client.Connected)
                        this.client.GetStream().Close(500);
                    else
                        this.client.Close();
                }
            }
            catch (ObjectDisposedException)
            {
                this.client.Close();
            }

            if (this.UserDisconnected != null)
                this.UserDisconnected(this);
        }
    }

    public void SendMemoryStream(System.IO.MemoryStream ms)
    {
        lock (ms)
        {
            int bytesWritten = (int)ms.Position;
            byte[] result = new byte[bytesWritten];

            ms.Position = 0;
            ms.Read(result, 0, bytesWritten);

            try
            {
                //Lock the stream and send away data asyncrounously
                //We can register a callback-method to be called when the data has been received like we did with 
                //BeginRead but in this example there is no need to know that the data has
                //been sent so we set it to null.
                //
                //BeginWrite can throw an exception when the client has been disconnected or when you
                //pass a buffer with incorrect size, we only catch the ones that occur when you've got disconnected
                lock (client.GetStream())
                    this.client.GetStream().BeginWrite(result, 0, result.Length, null, null);
            }
            catch (System.IO.IOException)
            {
                this.Disconnect();
            }
            catch (InvalidOperationException)
            {
                this.Disconnect();
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }

    public void SendMessage(Rites.Networking.RitesProtocol messageType, string messageData)
    {
        System.IO.MemoryStream ms = Util.CreateMessage(messageType, messageData, out LastSendMessageID, 0);
        SendMemoryStream(ms);
    }
}
